iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Odoo

Odoo 14 Javascript 開發心路歷程系列 第 21

Day 21 RPC 介紹 - 底層基本面講述

  • 分享至 

  • xImage
  •  

昨天先講了使用方式

接著來講一下底層的部分,以及 _rpc 是怎麼出現的

會額外提是因為筆者剛接觸時

會看到有些模組是直接 require('web.ajax')

下意識會覺得跟 _rpc 一樣,但又說不出有為什麼相同

首先,先全域搜尋關鍵字 _rpc: function ,可以找到定義位置

// addons/web/static/src/js/core/service_mixins.js
_rpc: function (params, options) {
    var query = rpc.buildQuery(params);
    var prom = this.call('ajax', 'rpc', query.route, query.params, options, this);
    if (!prom) {
        prom = new Promise(function () {});
        prom.abort = function () {};
    }
    var abort = prom.abort ? prom.abort : prom.reject;
    if (!abort) {
        throw new Error("a rpc promise should always have a reject function");
    }
    prom.abort = abort.bind(prom);
    return prom;
},

可以發現主要的兩行,其一是 rpc ,其二是 call()

var query = rpc.buildQuery(params);
var prom = this.call('ajax', 'rpc', query.route, query.params, options, this);

第一個 rpc 就很簡單了,跳到宣告點,就能發現就是 require 的結果

// addons/web/static/src/js/core/service_mixins.js
odoo.define('web.ServicesMixin', function (require) {
"use strict";

var rpc = require('web.rpc');

// ...

web.rpc 主要的功能就是組合 method , model … 等等內容,符合 odoo 本身底層的統一資料包

為什麼會這麼說呢?

看一下 buildQuery() 內容就知道了

// addons/web/static/src/js/core/rpc.js
buildQuery: function (options) {
    var route;
    var params = options.params || {};
    var orderBy;
    if (options.route) {
        route = options.route;
    } else if (options.model && options.method) {
        route = '/web/dataset/call_kw/' + options.model + '/' + options.method;
    }
    if (options.method) {
        params.args = options.args || [];
        params.model = options.model;
        params.method = options.method;
        params.kwargs = _.extend(params.kwargs || {}, options.kwargs);
        params.kwargs.context = options.context || params.context || params.kwargs.context;
    }

    if (options.method === 'read_group' || options.method === 'web_read_group') {
        if (!(params.args && params.args[0] !== undefined)) {
            params.kwargs.domain = options.domain || params.domain || params.kwargs.domain || [];
        }
        if (!(params.args && params.args[1] !== undefined)) {
            params.kwargs.fields = options.fields || params.fields || params.kwargs.fields || [];
        }
        if (!(params.args && params.args[2] !== undefined)) {
            params.kwargs.groupby = options.groupBy || params.groupBy || params.kwargs.groupby || [];
        }
        params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
        params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
        // In kwargs, we look for "orderby" rather than "orderBy" (note the absence of capital B),
        // since the Python argument to the actual function is "orderby".
        orderBy = options.orderBy || params.orderBy || params.kwargs.orderby;
        params.kwargs.orderby = orderBy ? rpc._serializeSort(orderBy) : orderBy;
        params.kwargs.lazy = 'lazy' in options ? options.lazy : params.lazy;

        if (options.method === 'web_read_group') {
            params.kwargs.expand = options.expand || params.expand || params.kwargs.expand;
            params.kwargs.expand_limit = options.expand_limit || params.expand_limit || params.kwargs.expand_limit;
            var expandOrderBy = options.expand_orderby || params.expand_orderby || params.kwargs.expand_orderby;
            params.kwargs.expand_orderby = expandOrderBy ? rpc._serializeSort(expandOrderBy) : expandOrderBy;
        }
    }

    if (options.method === 'search_read') {
        // call the model method
        params.kwargs.domain = options.domain || params.domain || params.kwargs.domain;
        params.kwargs.fields = options.fields || params.fields || params.kwargs.fields;
        params.kwargs.offset = options.offset || params.offset || params.kwargs.offset;
        params.kwargs.limit = options.limit || params.limit || params.kwargs.limit;
        // In kwargs, we look for "order" rather than "orderBy" since the Python
        // argument to the actual function is "order".
        orderBy = options.orderBy || params.orderBy || params.kwargs.order;
        params.kwargs.order = orderBy ? rpc._serializeSort(orderBy) : orderBy;
    }

    if (options.route === '/web/dataset/search_read') {
        // specifically call the controller
        params.model = options.model || params.model;
        params.domain = options.domain || params.domain;
        params.fields = options.fields || params.fields;
        params.limit = options.limit || params.limit;
        params.offset = options.offset || params.offset;
        orderBy = options.orderBy || params.orderBy;
        params.sort = orderBy ? rpc._serializeSort(orderBy) : orderBy;
        params.context = options.context || params.context || {};
    }

    return {
        route: route,
        params: JSON.parse(JSON.stringify(params)),
    };
},

有沒有發現前面的判斷,有 modelmethod 時, route 會有額外組合

// addons/web/static/src/js/core/rpc.js
// ...
if (options.route) {
    route = options.route;
} else if (options.model && options.method) {
    route = '/web/dataset/call_kw/' + options.model + '/' + options.method;
}
// ...

而有些模組會是直接 const rpc = require('web.rpc')

這樣也是可以,但要注意的是呼叫的函式不是 rpc() ,是 query()

所以 _rpc()rpc.query() 做的內容其實是一樣的

只是前者做了額外的錯誤處理

接著第二個 call()

this.call('ajax', 'rpc', query.route, query.params, options, this);

看一下 call() 的函式,會發現是 trigger_up 服務 ajax

// addons/web/static/src/js/core/service_mixins.js
call: function (service, method) {
    var args = Array.prototype.slice.call(arguments, 2);
    var result;
    this.trigger_up('call_service', {
        service: service,
        method: method,
        args: args,
        callback: function (r) {
            result = r;
        },
    });
    return result;
},

因為中間跳了幾層,所以省略了,有興趣的朋友可以自行翻一下

總之可以看在 web 模組的 service 的註冊

// addons/web/static/src/js/services/ajax_service.js
odoo.define('web.AjaxService', function (require) {
"use strict";

var AbstractService = require('web.AbstractService');
var ajax = require('web.ajax');
var core = require('web.core');
var session = require('web.session');

var AjaxService = AbstractService.extend({
    /**
     * @param {Object} libs - @see ajax.loadLibs
     * @param {Object} [context] - @see ajax.loadLibs
     * @param {Object} [tplRoute] - @see ajax.loadLibs
     */
    loadLibs: function (libs, context, tplRoute) {
        return ajax.loadLibs(libs, context, tplRoute);
    },
    rpc: function (route, args, options, target) {
        var rpcPromise;
        var promise = new Promise(function (resolve, reject) {
            rpcPromise = session.rpc(route, args, options);
            rpcPromise.then(function (result) {
                if (!target.isDestroyed()) {
                    resolve(result);
                }
            }).guardedCatch(function (reason) {
                if (!target.isDestroyed()) {
                    reject(reason);
                }
            });
        });
        promise.abort = rpcPromise.abort.bind(rpcPromise);
        return promise;
    },
});

core.serviceRegistry.add('ajax', AjaxService);

return AjaxService;

});

從中不難發現就是執行了這個服務的 rpc()

但各位有沒有注意到,這幾個檔案追蹤到最後都有 require('web.ajax')

odoo 底層已經有包裝好往後端溝通的函式了,就不用重複造輪子

各位有興趣可以自行看一下 addons/web/static/src/js/core/ajax.js

最後為什麼要有 web.ajaxweb.rpc 的差別

簡單來說就是開發時寫的內容多寡

如果是使用 web.ajaxurl 的部分就要寫的完整

web.rpc 已經有包裝相關內容了,就不用這麼麻煩

各位可以思考一下下面的程式

例子比較簡單,但內容若是比較多的時候,而且要重複寫很多次

那麼哪一個會比較能夠容易懂

const ajax = require('web.ajax');
const rpc = require('web.rpc');

ajax.rpc(
    "/web/dataset/call_kw/res.users/read",
    {
        args: [[1001, 1002, 1003]],
    }
)

rpc.query({
    model: 'res.partner',
    method: 'read',
    args: [[1001, 1002, 1003]]
})

上一篇
Day 20 RPC 介紹 - 使用方式介紹
下一篇
Day 22 實作 10: 建立 action client view - 增加事件(上)
系列文
Odoo 14 Javascript 開發心路歷程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言